import * as method from "@mac-xiang/method";
import axios from "axios";
import FormData from "form-data";
import Alipay from "alipay-sdk";
import AlipayFormData from "alipay-sdk/lib/form";
import cheerio from "cheerio";
// snakeCaseKeys 将驼峰改为下划线
import { Callback, oa } from "../itf";
import { ORDER, REQUEST } from "../struct";

import * as itf from "./itf";

export class PAY {
  type = ["NATIVE", "JSAPI", "APP", "NWEB"];
  config: itf.Config;
  alipay: Alipay;
  constructor (param: itf.Create) {
    this.config = param;
    this.alipay = new Alipay({
      appId: this.config.appId,
      privateKey: this.config.privateKey,
      alipayPublicKey: this.config.publicKey,
      gateway: this.config.gateway,
      charset: "utf-8",
      version: "1.0",
      signType: "RSA2",
      encryptKey: this.config.encryptKey,
    });
  }
  private Request(p: string, order?: string) {
    return new Promise<REQUEST>(async (resolve) => {
      const ch = cheerio.load(""), dom = ch.parseHTML(p);
      const dr = dom[(dom as Array<oa>).findIndex((a) => { return a.name == "form"; })];
      const form = new FormData();
      const body: oa = {};
      dr.childNodes.filter((a: oa) => {
        return a.name == "input";
      }).forEach((a: oa) => {
        form.append(a.attribs.name, a.attribs.value);
        try {
          body[a.attribs.name] = JSON.parse(a.attribs.value);
        } catch (error) {
          body[a.attribs.name] = a.attribs.value;
        }
      });
      const ret = (a: oa) => {
        const p: oa = {};
        if (!!order) p.order = order;
        const r = new REQUEST(Object.assign(p, a));
        resolve(r);
      };

      if ((dr as oa).attribs && typeof (dr as oa).attribs.action == "string") {
        axios({
          method: "post",
          url: (dr as oa).attribs.action,
          data: form,
          headers: form.getHeaders()
        }).then(r => {
          const d = r.data.alipay_trade_query_response ? r.data.alipay_trade_query_response : r.data;
          ret(
            d.alipay_trade_close_response ||
            d.alipay_trade_refund_response ||
            d
          );
        }).catch(e => {
          ret({ err: "网络错误", raw: e });
        });
      } else ret({ err: "解析错误", raw: p });
    });
  }
  private orderMethod(p: oa) {
    return new Promise<REQUEST>(async (resolve) => {
      const msd = p.method as string;
      delete p.method;
      if (p.order) {
        p.out_trade_no = p.order;
        delete p.order;
      }
      const formData = new AlipayFormData();
      formData.addField('bizContent', p);
      const html = await this.alipay.exec("alipay.trade." + msd, {}, { formData });
      let a: oa = { err: "网络错误", raw: html };
      if (typeof html == "string") {
        a = await this.Request(html, p.out_trade_no);
      }
      const r = new REQUEST(a);
      resolve(r);
    });
  }
  create(p: ORDER) {
    return new Promise<REQUEST>(async resolve => {
      const app = p.trade_type == "APP";
      const q: oa = {
        OutTradeNo: p.order, // OutTradeNo out_trade_no
        ProductCode: app ? "QUICK_MSECURITY_PAY" : "FAST_INSTANT_TRADE_PAY", // ProductCode product_code
        TotalAmount: parseInt(p.money.toString()) / 100, // TotalAmount total_amount
        Subject: p.title, // Subject
        TimeoutExpress: "5m", // TimeoutExpress timeout_express
        NotifyUrl: p.backUrl ? p.backUrl : this.config.notifyUrl, // NotifyUrl notify_url
        // Body
      };
      if (p.pack_params) q.pack_params = JSON.stringify(p.pack_params);
      const formData = new AlipayFormData();
      // formData.addField('notifyUrl', q.NotifyUrl);
      formData.addField('notifyUrl', app ? "" : q.NotifyUrl);
      formData.addField('bizContent', q);
      if (app) formData.setMethod("get");
      let ret: oa = { err: "下单错误" };
      try {
        const result = await this.alipay.exec(`alipay.trade.${app ? "app" : "page"}.pay`, {}, { formData });
        ret = {
          order: q.OutTradeNo, // 商户订单号
          money: parseInt(p.money.toString()), // 金额,单位分
          respCode: "00",
          body: result
        };
        if (app && typeof result == "string") {
          ret.app = result.substr(result.indexOf("?") + 1);
        }
      } catch (error) {
        ret = { err: "网络错误", raw: error };
      }
      resolve(new REQUEST(ret));
    });
  };

  async query(order: string): Promise<REQUEST> {
    return await this.orderMethod({
      out_trade_no: order,
      method: "query"
    });
  };
  async close(order: string): Promise<REQUEST> {
    return await this.orderMethod({
      out_trade_no: order,
      method: "close"
    });
  };
  back(p: ORDER): Promise<REQUEST> {
    return new Promise(async resolve => {
      let ret = { err: "参数错误", raw: p };
      const rr = (a?: oa, b?: oa) => {
        const r = new REQUEST(a ? (b ? Object.assign(b, a) : a) : ret);
        resolve(r);
      };
      if (typeof p.order != "string") return rr();
      let money = parseInt(p.money.toString()) / 100;
      if (!(money > 0)) money = 0;
      if (!money) {
        let t: any = await this.query(p.order);
        if (t.check) money = t.payment;
      }
      if (!money) return rr();
      if (typeof p.orderT != "string") p.orderT = method.randStr("al");

      const q: oa = {
        method: "refund",
        out_trade_no: p.order,
        refund_amount: money,
        refund_reason: p.title,
        out_request_no: p.orderT,
        notify_url: p.backUrl ? p.backUrl : this.config.notifyUrl,
      };
      resolve(await this.orderMethod(q));
    });
  };
  notify(req: oa): Promise<REQUEST> {
    return new Promise(async resolve => {
      let ret: oa = { err: "验签失败", raw: req };
      if (this.alipay.checkNotifySign(req)) {
        delete req.sign;
        ret = req;
      }
      const r = new REQUEST(ret);
      resolve(r);
    });
  };
  async front(order: string): Promise<REQUEST> {
    return await this.query(order);
  }
}
export default PAY;